﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using FlickrNet;
using igather.Framework;
using igather.Framework.Clustering;
using igather.Framework.Search;
using MathNet.Numerics.LinearAlgebra;

namespace igather.Search.FlickrSearchEngine
{
   public class FlickrSearchEngine : ISearchEngine
   {
		private const int PAGES = 1;
		private const double MIN_GRAPH_COVERAGE = 0.1;
		private const string APIKEY = "af9955312f6fee6d380f4e81aedd695c";
		private const int THUMBS = 4;
				
		private struct Pair
		{
			public Vertex v1;
			public Vertex v2;
		}
		
		private Dictionary<Vertex, int> mNodeCount;
		private Dictionary<Pair, int> mPairCount;
		private Dictionary<Vertex, List<Pair>> mPairsPerVertex;
		private string[] mSearchTags;
		
		public Photos FlickrRawResults
		{
			get;
			set;
		}
		
       	public  FlickrSearchEngine()
       	{
			
       	}
		
		public List<string> SearchThumbs(string tags)
		{
			Flickr flickr = new Flickr(APIKEY);
			
			PhotoSearchOptions searchOptions = new PhotoSearchOptions();
			searchOptions.Tags = tags;
			searchOptions.Extras = PhotoSearchExtras.Tags;
			searchOptions.PerPage = THUMBS;
			searchOptions.Page = 0;
			searchOptions.SortOrder = PhotoSearchSortOrder.InterestingnessDesc;
			searchOptions.TagMode = TagMode.AllTags;
			Photos results = flickr.PhotosSearch(searchOptions);
			Console.WriteLine("Search for {0} returned {1} results out of {2}", tags, results.TotalPhotos, THUMBS);
			List<string> thumbs = new List<string>();
			foreach (Photo p in results.PhotoCollection)
				thumbs.Add(p.SquareThumbnailUrl);
			return thumbs;
		}

       	public IGraph Search(string tags, int results, int page)
       	{
           	Flickr flickr = new Flickr(APIKEY);

			System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
			sw.Start();
          	PhotoSearchOptions searchOptions = new PhotoSearchOptions();
       		searchOptions.Tags = tags;
            searchOptions.Extras = PhotoSearchExtras.Tags | PhotoSearchExtras.OwnerName | PhotoSearchExtras.DateUploaded;
            searchOptions.PerPage = results;
			searchOptions.Page = page;
			searchOptions.SortOrder = PhotoSearchSortOrder.InterestingnessDesc;
			searchOptions.TagMode = TagMode.AllTags;
           	Photos searchPhotos = flickr.PhotosSearch(searchOptions);
			FlickrRawResults = searchPhotos;
			sw.Stop();
			Console.WriteLine(string.Format("Flickr search: {0}", sw.Elapsed));
			mSearchTags = tags.Split(',');
			mNodeCount = new Dictionary<Vertex, int>();
			mPairCount = new Dictionary<Pair, int>();
			mPairsPerVertex = new Dictionary<Vertex, List<Pair>>();
           	return ConvertToGraph(searchPhotos);
       	}

       	private IGraph ConvertToGraph(Photos photos)
       	{
			IGraph g = GraphFactory.CreateGraph();
			Vertex v;
			
			System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
			sw.Start();
           	foreach (Photo photo in photos.PhotoCollection)
           	{
				string[] tags = photo.CleanTags.Split(' ');
				Array.Sort<string>(tags);
			   	foreach (string tag1 in tags)
			   	{
					if (Array.Exists<string>(mSearchTags, (string s) => (s.Equals(tag1))))
						continue;
					v = new Vertex(tag1);
					if (!mNodeCount.ContainsKey(v))
					{
						v.Url = photo.SquareThumbnailUrl;
						mNodeCount.Add(v, 0);
						mPairsPerVertex.Add(v, new List<Pair>());
					}
					mNodeCount[v]++;
					foreach (string tag2 in tags)
					{
						Pair p = new Pair();
						p.v1 = v;
						p.v2 = new Vertex(tag2);
						if (!mPairCount.ContainsKey(p))
							mPairCount.Add(p, 0);
						mPairCount[p]++;
						mPairsPerVertex[v].Add(p);
					}
			   	}
		   	}
			sw.Stop();
			Console.WriteLine(string.Format("Result mapping: {0}", sw.Elapsed));
			
			sw.Reset();
			sw.Start();
			foreach (Vertex vertex in mNodeCount.Keys)
				if (mNodeCount[vertex] > 1)
					g.AddVertex(vertex);
			foreach (Vertex vertex in g.Vertices)
				foreach (Pair p in mPairsPerVertex[vertex])
					if ((g.Vertices.Contains(p.v2)) && (!g.GetEdges(vertex).Contains(g.CreateEdge(p.v1, p.v2))))
						g.AddEdge(
						          g.CreateEdge(
						                       p.v1,
						                       p.v2,
						                       (2d*mPairCount[p])/(mNodeCount[p.v1]+mNodeCount[p.v2])
						                       )
						          );
			sw.Stop();
			Console.WriteLine(string.Format("Graph assembly: {0}", sw.Elapsed));
			
			//sw.Reset();
			//sw.Start();
			//ClusteredGraph clusters = GetClusters(g.ToMatrix(), g);
			//foreach (Cluster c in clusters.Clusters)
			//{
			//	if (c.Vertices.Count < (g.Vertices.Count * MIN_GRAPH_COVERAGE))
			//		foreach (Vertex vertex in c.Vertices)
			//			g.RemoveVertex(vertex);
			//}
			//sw.Stop();
			//Console.WriteLine("Removal of small components: {0}", sw.Elapsed);
           	return g;
       	}
		
		private ClusteredGraph GetClusters(Matrix m, IGraph g)
		{
			ClusteredGraph result = new ClusteredGraph();
			Cluster c;
			Stack s = new Stack();
			int current;
			bool withElem;

			for (int row = 0; row < m.RowCount; row++)
			{
				withElem = false;
				for (int col = 0; col < m.ColumnCount; col++)
				{
					if (m[row, col] > 0)
					{
						withElem = true;
						break;
					}
				}
				if (withElem)
				{
					s.Push(row);
					c = new Cluster(g);
					do
					{
						current = (int)s.Pop();
						Vertex v = g.Vertices[current];
						if (!c.Vertices.Contains(v))
						{
							List<Edge> edges = g.GetEdges(v);
							c.AddVertex(v);
							foreach (Edge e in edges)
								c.AddEdge(e);
						}

						for (int col = 0; col < m.ColumnCount; col++)
							if ((m[current, col] > 0) || (m[col, current] > 0))
							{
								m[current, col] = 0;
								m[col, current] = 0;
								if (current != col)
									s.Push(col);
							}
					} while (s.Count > 0);
					result.Clusters.Add(c);
				}
			}
            return result;
		}
   	}
}
